﻿using System;
using System.ComponentModel;
using System.Data.SqlClient;
using System.Data;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.Diagnostics;
using CSW.Framework.Common;
using CSW.Framework.Common.Binding;
using CSW.Framework.Common.Data;

namespace LinqBusinessObjectBuilder.ProjectInfo
{
    [Serializable]
    internal class Project : BusinessObject, ISerializable
    {
        private readonly Property<string> m_Server;
        private readonly Property<AuthenticationMode> m_AuthenticationMode;
        private readonly Property<string> m_User;
        private readonly Property<string> m_Password;
        private readonly Property<string> m_Database;
        private readonly Property<string> m_Namespace;
        private readonly Property<string> m_DataContextName;
        private readonly Property<SerializationMode> m_SerializationMode;
        private readonly Property<bool> m_UsePluralize;
        private readonly Property<bool> m_GenerateStoredProcedures;
        private readonly Property<bool> m_GenerateViews;
        private readonly Property<bool> m_GenerateFunctions;
        private readonly Property<string> m_OutputPath;
        private readonly Property<string> m_ProjectFilePath;
        private readonly Property<string> m_SolutionName;

        private readonly Property<ReadOnlyBindingList<string>> m_DatabaseList;
        private readonly ReadOnlyBindingList<string>.ListModifier m_DatabaseListModifier;

        private string m_UserBackup;
        private string m_PasswordBackup;
        private bool m_IsNew = true;

        public Project()
        {
            m_Server = new Property<string>(this, "Server");
            m_AuthenticationMode = new Property<AuthenticationMode>(this, "AuthenticationMode", AuthenticationMode.WindowsAuthentication);
            m_User = new Property<string>(this, "User");
            m_Password = new Property<string>(this, "Password");
            m_Database = new Property<string>(this, "Database");
            m_Namespace = new Property<string>(this, "Namespace");
            m_DataContextName = new Property<string>(this, "DataContextName");
            m_SerializationMode = new Property<SerializationMode>(this, "SerializationMode", SerializationMode.None);
            m_UsePluralize = new Property<bool>(this, "UsePluralize");
            m_GenerateStoredProcedures = new Property<bool>(this, "GenerateStoredProcedures");
            m_GenerateViews = new Property<bool>(this, "GenerateViews");
            m_GenerateFunctions = new Property<bool>(this, "GenerateFunctions");
            m_OutputPath = new Property<string>(this, "OutputPath");
            m_ProjectFilePath = new Property<string>(this, "ProjectFilePath");
            m_SolutionName = new Property<string>(this, "SolutionName");

            m_DatabaseList = new Property<ReadOnlyBindingList<string>>(this, "DatabaseList", new ReadOnlyBindingList<string>());
            m_DatabaseListModifier = m_DatabaseList.Value.MakeReadOnly();

            m_AuthenticationMode.PropertyChanged += m_AuthenticationMode_PropertyChanged;
            m_Database.PropertyChanged += m_Database_PropertyChanged;
            m_OutputPath.PropertyChanged += m_OutputPath_PropertyChanged;

            AddRequiredField(m_ProjectFilePath, "Project File Path is required.");
        }

        private void m_OutputPath_PropertyChanged(ComplexProperty<string> sender, BaseProperty<string>.PropertyChangedEventArgs e)
        {
            if (e.NewValue != e.OldValue && !string.IsNullOrEmpty(e.NewValue))
            {
                string oldDirectory = null;
                if (!string.IsNullOrEmpty(e.OldValue))
                    oldDirectory = Path.GetFileName(e.OldValue);

                if (string.IsNullOrEmpty(SolutionName) || SolutionName == oldDirectory)
                {
                    SolutionName = Path.GetFileName(e.NewValue);
                }
            }
        }

        private void m_Database_PropertyChanged(ComplexProperty<string> sender, BaseProperty<string>.PropertyChangedEventArgs e)
        {
            if (!string.IsNullOrEmpty(e.NewValue) && e.NewValue != e.OldValue)
            {
                if (DataContextName == e.OldValue || string.IsNullOrEmpty(DataContextName))
                    DataContextName = e.NewValue;

                if (Namespace == e.OldValue + ".Business" || string.IsNullOrEmpty(Namespace))
                    Namespace = e.NewValue + ".Business";
            }
        }

        private void m_AuthenticationMode_PropertyChanged(object sender, EventArgs e)
        {
            if (AuthenticationMode.WindowsAuthentication.Equals(AuthenticationMode))
            {
                m_UserBackup = m_User.Value;
                m_PasswordBackup = m_Password.Value;

                m_User.Value = null;
                m_Password.Value = null;

                m_User.IsReadOnly = true;
                m_Password.IsReadOnly = true;
            }
            else if (AuthenticationMode.SqlServerAuthentication.Equals(AuthenticationMode))
            {
                m_User.IsReadOnly = false;
                m_Password.IsReadOnly = false;

                if (string.IsNullOrEmpty(m_User.Value))
                    m_User.Value = m_UserBackup;
                if (string.IsNullOrEmpty(m_Password.Value))
                m_Password.Value = m_PasswordBackup;
            }
        }

        public string ProjectFilePath
        {
            get { return m_ProjectFilePath.Value; }
            set { m_ProjectFilePath.Value = value; }
        }

        public string SolutionName
        {
            get { return m_SolutionName.Value; }
            set { m_SolutionName.Value = value; }
        }

        public string Server
        {
            get { return m_Server.Value; }
            set { m_Server.Value = value; }
        }

        public AuthenticationMode AuthenticationMode
        {
            get { return m_AuthenticationMode.Value; }
            set { m_AuthenticationMode.Value = value; }
        }

        public string User
        {
            get { return m_User.Value; }
            set { m_User.Value = value; }
        }

        public string Password
        {
            get { return m_Password.Value; }
            set { m_Password.Value = value; }
        }

        public string Database
        {
            get { return m_Database.Value; }
            set { m_Database.Value = value; }
        }

        public string Namespace
        {
            get { return m_Namespace.Value; }
            set { m_Namespace.Value = value; }
        }

        public string DataContextName
        {
            get { return m_DataContextName.Value; }
            set { m_DataContextName.Value = value; }
        }

        public SerializationMode SerializationMode
        {
            get { return m_SerializationMode.Value; }
            set { m_SerializationMode.Value = value; }
        }

        public bool UsePluralize
        {
            get { return m_UsePluralize.Value; }
            set { m_UsePluralize.Value = value; }
        }

        public bool GenerateStoredProcedures
        {
            get { return m_GenerateStoredProcedures.Value; }
            set { m_GenerateStoredProcedures.Value = value; }
        }

        public bool GenerateViews
        {
            get { return m_GenerateViews.Value; }
            set { m_GenerateViews.Value = value; }
        }

        public bool GenerateFunctions
        {
            get { return m_GenerateFunctions.Value; }
            set { m_GenerateFunctions.Value = value; }
        }
        
        public BindingList<string> DatabaseList
        {
            get { return m_DatabaseList.Value; }
        }

        public string OutputPath
        {
            get { return m_OutputPath.Value; }
            set { m_OutputPath.Value = value; }
        }

        public void RefreshDatabases()
        {
            SqlConnectionStringBuilder sql = new SqlConnectionStringBuilder();
            sql.DataSource = Server;
            sql.InitialCatalog = "master";
            if (User != null) sql.UserID = User;
            if (Password != null) sql.Password = Password;
            sql.ConnectTimeout = 10;
            sql.IntegratedSecurity = AuthenticationMode.WindowsAuthentication.Equals(AuthenticationMode);

            using (SqlConnection conn = new SqlConnection(sql.ToString()))
            {
                conn.Open();

                DataTable dataTable = conn.GetSchema("databases");

                string databaseBackup = Database;

                m_DatabaseListModifier.ClearItems();
                
                string[] excludeDatabases = new[] { "master", "model", "msdb", "tempdb" };

                foreach (string database in dataTable.Select()
                    .Select(p => DataConverter.GetString(p[0]))
                    .Where(p => !excludeDatabases.Contains(p))
                    .OrderBy(p => p))
                {
                    m_DatabaseListModifier.InsertItem(m_DatabaseList.Value.Count, database);

                    // Set database to previous selection
                    if (database == databaseBackup)
                        Database = database;
                }

                // Set database to first in list if none selected
                if (Database == null)
                {
                    if (DatabaseList.Count > 0)
                        Database = DatabaseList[0];
                }
            }
        }

        #region ISerializable Members

        protected Project(SerializationInfo info, StreamingContext context)
            : this()
        {
            Deserialize(info);
        }

        private SerializationInfo m_DeserializationInfo;
        private void Deserialize(SerializationInfo info)
        {
            Guard.ArgumentNotNull(info, "info");
            
            m_DeserializationInfo = info;

            BeginLoading();
            try
            {
                double version;
                if (!double.TryParse(info.GetString("Project/Version"), out version))
                    version = 1.0;

                Server = info.GetString("Project/Server");
                User = info.GetString("Project/User");
                Password = info.GetString("Project/Password");
                Database = info.GetString("Project/Database");
                Namespace = info.GetString("Project/Namespace");
                DataContextName = info.GetString("Project/DataContextName");
                UsePluralize = info.GetBoolean("Project/UsePluralize");
                GenerateStoredProcedures = info.GetBoolean("Project/GenerateStoredProcedures");
                GenerateViews = info.GetBoolean("Project/GenerateViews");
                GenerateFunctions = info.GetBoolean("Project/GenerateFunctions");

                if (SerializationMode.Unidirectional.Name == info.GetString("Project/SerializationMode"))
                    SerializationMode = SerializationMode.Unidirectional;
                else
                    SerializationMode = SerializationMode.None;

                if (AuthenticationMode.SqlServerAuthentication.Name == info.GetString("Project/AuthenticationMode"))
                    AuthenticationMode = AuthenticationMode.SqlServerAuthentication;
                else
                    AuthenticationMode = AuthenticationMode.WindowsAuthentication;

                if (version >= 1.1)
                {
                    OutputPath = info.GetString("Project/OutputPath");
                }

                if (version >= 1.2)
                {
                    SolutionName = info.GetString("Project/SolutionName");
                }

                try
                {
                    RefreshDatabases();
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex);
                }
            }
            finally
            {
                m_IsNew = false;
                EndLoading();
            }
        }

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            string errorMessage;
            if (!CheckAllRules(out errorMessage))
                throw new InvalidOperationException(errorMessage);

            info.AddValue("Project/Version", "1.2");
            info.AddValue("Project/Server", Server);
            info.AddValue("Project/AuthenticationMode", AuthenticationMode.Name);
            info.AddValue("Project/User", User);
            info.AddValue("Project/Password", Password);
            info.AddValue("Project/Database", Database);
            info.AddValue("Project/Namespace", Namespace);
            info.AddValue("Project/DataContextName", DataContextName);
            info.AddValue("Project/SerializationMode", SerializationMode.Name);
            info.AddValue("Project/UsePluralize", UsePluralize);
            info.AddValue("Project/GenerateStoredProcedures", GenerateStoredProcedures);
            info.AddValue("Project/GenerateViews", GenerateViews);
            info.AddValue("Project/GenerateFunctions", GenerateFunctions);
            info.AddValue("Project/OutputPath", OutputPath);
            info.AddValue("Project/SolutionName", SolutionName);
        }

        #endregion

        public static Project LoadFromFile(string fileName)
        {
            Guard.ArgumentNotNullOrEmptyString(fileName, "fileName");

            BinaryFormatter formatter = new BinaryFormatter();

            Project project;
            using (FileStream fs = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                project = (Project)formatter.Deserialize(fs);
            }

            project.BeginLoading();
            try
            {
                project.ProjectFilePath = fileName;
            }
            finally
            {
                project.EndLoading();
            }

            return project;
        }

        /// <summary>
        /// Tries to save the record to the database.
        /// </summary>
        /// <param name="errorMessage">The error message.</param>
        /// <returns><c>true</c> if successful; otherwise, <c>false</c>.</returns>
        public override bool TrySave(out string errorMessage)
        {
            if (!CheckAllRules(out errorMessage))
                return false;

            BinaryFormatter formatter = new BinaryFormatter();

            using (FileStream fs = File.Open(ProjectFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                formatter.Serialize(fs, this);
            }

            IsEditting = false;

            return true;
        }

        public override bool IsNew
        {
            get { return m_IsNew; }
        }

        public override void Refresh()
        {
            if (!string.IsNullOrEmpty(ProjectFilePath))
            {
                Project project = LoadFromFile(ProjectFilePath);
                Deserialize(project.m_DeserializationInfo);
            }
        }
    }
}